import { App, Config, Provide, Init } from '@midwayjs/decorator'
import { Application } from 'egg'
import { customAlphabet } from 'nanoid'
import { gql } from 'graphql-request'

import { streamToBuffer } from '../utils/helper'

const path = require('path')
const dayjs = require('dayjs')
const Minio = require('minio')
const sendToWormhole = require('stream-wormhole')

@Provide()
export class UploadService {

  protected minioClient: any
  protected nanoid: () => string

  @App()
  app: Application

  @Config('minio')
  minioConfig: any

  @Init()
  async init() {
    const { bucketName, ...clientConfig } = this.minioConfig

    this.minioClient = new Minio.Client({
      ...clientConfig,
      useSSL: false
    })

    try {
      const exists = await this.minioClient.bucketExists(bucketName)

      if (!exists) {
        await this.minioClient.makeBucket(bucketName)
      }
    } catch (err) {}

    this.nanoid = customAlphabet('1234567890abcdef', 32)
  }

  async get(id) {
    try {
      const { ie_attachment_by_pk } = await this.app.gqlRequest(gql`
        query ($id: Int!) {
          ie_attachment_by_pk (id: $id) {
            ext
            filename
            filepath
            id
            create_at
          }
        }
      `, { id })

      return ie_attachment_by_pk
    } catch (err) {}
  }

  async upload(stream) {
    const { bucketName } = this.minioConfig
    const { filename } = stream

    this.app.logger.info(`Received ${filename}`)

    const ext = path.extname(filename).toLowerCase()
    const name = dayjs().format('YYYYMMDD') + '/' + this.nanoid() + ext

    let filepath

    try {
      const metaData = {}

      if (ext === '.pdf') {
        metaData['Content-Type'] = 'application/pdf'
      }

      await this.minioClient.putObject(bucketName, name, stream, null, metaData)

      filepath = `/${bucketName}/${name}`

      const {
        insert_ie_attachment: {
          returning: [added_attachment]
        }
      } = await this.app.gqlRequest(gql`
        mutation ($attachment: ie_attachment_insert_input!) {
          insert_ie_attachment (
            objects: [$attachment]
          ) {
            returning {
              id
              create_at
            }
          }
        }
      `, {
        attachment: {
          filename,
          filepath,
          ext
        }
      })

      if (!added_attachment) return

      this.app.logger.info(`Stored at ${filepath}`)

      return {
        filename,
        filepath,
        ext,
        ...added_attachment
      }
    } catch (err) {
      this.app.logger.error(err)
      await sendToWormhole(stream)
    }
  }

  async remove(id) {
    try {
      const { bucketName } = this.minioConfig
      const {
        ie_attachment_by_pk: {
          filepath
        }
      } = await this.app.gqlRequest(gql`
        query ($id: Int!) {
          ie_attachment_by_pk (id: $id) {
            filepath
          }
        }
      `, { id })

      const name = filepath.replace(`/${bucketName}/`, '')

      this.app.logger.info(`Deleting ${name}`)

      await this.minioClient.removeObject(bucketName, name)

      this.app.logger.info(`Deleted ${name}`)

      await this.app.gqlRequest(gql`
        mutation ($id: Int!) {
          delete_ie_attachment_by_pk(id: $id) {
            id
          }
        }
      `, { id })

      return id
    } catch (err) {}
  }

  async download(idOrUri) {
    const { bucketName } = this.minioConfig

    try {
      let filepath

      if (
        typeof idOrUri === 'string' &&
        ~idOrUri.indexOf(bucketName)
      ) {
        filepath = idOrUri
      } else {
        const {
          ie_attachment_by_pk
        } = await this.app.gqlRequest(gql`
          query ($id: Int!) {
            ie_attachment_by_pk (id: $id) {
              filepath
            }
          }
        `, { id: idOrUri })

        filepath = ie_attachment_by_pk?.filepath
      }

      const name = filepath.replace(`/${bucketName}/`, '')

      return await this.minioClient.getObject(bucketName, name)
    } catch (err) {}
  }

  async getBuffer(idOrUri) {
    const stream = await this.download(idOrUri)

    try {
      return await streamToBuffer(stream)
    } catch (err) {
      this.app.logger.error(err)
      await sendToWormhole(stream)
    }
  }
}
